package service

import (
	"context"
	"errors"
	"fmt"
	"github.com/wonderivan/logger"
	appsv1 "k8s.io/api/apps/v1"
	scalev1 "k8s.io/api/autoscaling/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/apimachinery/pkg/util/json"
	"k8s.io/apimachinery/pkg/util/yaml"
	"time"
)

var Deploy deploy

type deploy struct{}

type DeploysResp struct {
	Total int                 `json:"total"`
	Items []appsv1.Deployment `json:"items"`
}

type DeploysNp struct {
	Namespace string
	DeployNum int
}

type deployCell appsv1.Deployment

//重写deployCell的接口
func (d deployCell) GetCreation() time.Time {
	return d.CreationTimestamp.Time
}

func (d deployCell) GetName() string {
	return d.Name
}

func (d *deploy) toCells(deploys []appsv1.Deployment) []DataCell {
	cells := make([]DataCell, len(deploys))
	for i := range deploys {
		cells[i] = deployCell(deploys[i])
	}
	return cells
}

func (d *deploy) fromCells(cells []DataCell) []appsv1.Deployment {
	deploys := make([]appsv1.Deployment, len(cells))
	for i := range cells {
		deploys[i] = appsv1.Deployment(cells[i].(deployCell))
	}
	return deploys
}

func (d *deploy) GetDeploys(filterName, namespace string, limit, page int) (deploysResp *DeploysResp, err error) {
	//func (d *deploy)GetDeploys(namespace string){
	deployList, err := K8s.ClientSet.AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{})
	if err != nil {
		logger.Error("返回deploy列表失败, " + err.Error())
		return nil, errors.New("获取deploy列表失败，" + err.Error())
	}
	selectableData := &dataSelector{
		GenericDataList: d.toCells(deployList.Items),
		DataSelect: &DataSelectQuery{
			Filter: &FilterQuery{Name: filterName},
			Paginate: &PaginateQuery{
				Limit: limit,
				Page:  page,
			},
		},
	}

	filtered := selectableData.Filter()
	data := filtered.Sort().Paginate()
	total := len(data.GenericDataList)
	deploys := d.fromCells(data.GenericDataList)
	return &DeploysResp{
		Total: total,
		Items: deploys,
	}, nil
}

func (d *deploy) GetDeployDetail(deployname, namespace string) (deploy *appsv1.Deployment, err error) {
	deploy, err = K8s.ClientSet.AppsV1().Deployments(namespace).Get(context.TODO(), deployname, metav1.GetOptions{})
	if err != nil {
		logger.Error("get deploy detail failed," + err.Error())
		return nil, errors.New("get deploy detail failed," + err.Error())
	}
	return deploy, err
}

//定义Deploycreate结构体，用于创建deployment需要的参数属性的定义
type DeployCreate struct {
	Name          string            `json:"name"`
	Namespace     string            `json:"namespace"`
	Replicas      int32             `json:"replicas"`
	Image         string            `json:"image"`
	Command       []string          `json:"command"`
	Label         map[string]string `json:"label"`
	Cpu           string            `json:"cpu"`
	Memory        string            `json:"memory"`
	ContainerPort int32             `json:"container_port"`
	HealthCheck   bool              `json:"health_check"`
	HealthPath    string            `json:"health_path"`
}

//创建deploy示例：
/*
# 带有healthcheck
curl -XPOST -d '{"name":"nginxgo","namespace":"chaofeng","replicas":1,"image":"hub.guazi-cloud.com/library/nginx","label":{"app":"nginxgo"},"cpu":"200m","memory":"600M","container_port":80,"health_check":false}'  http://127.0.0.1:9090/api/k8s/deploy/create

# 不带有healthcheck
curl -XPOST -d '{"name":"nginxgo","namespace":"chaofeng","replicas":1,"image":"hub.guazi-cloud.com/library/nginx","label":{"app":"nginxgo"},"cpu":"200m","memory":"600M","container_port":80,"health_check":true,"health_path":"/"}'  http://127.0.0.1:9090/api/k8s/deploy/create

# 增加command
curl -XPOST -d '{"name":"mainv1","namespace":"kubespray","replicas":1,"image":"registry.cn-hangzhou.aliyuncs.com/kubespray-library/kubespray:v1","label":{"app":"mainv1"},"command": ["/bin/sh","-c","sleep 3600"],"cpu":"200m","memory":"600M","container_port":80,"health_check":false}'  http://127.0.0.1:12312/api/k8s/deploy/create
*/

func (d *deploy) CreateDeployment(data *DeployCreate) (err error) {
	deployment := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      data.Name,
			Namespace: data.Namespace,
			Labels:    data.Label,
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: &data.Replicas,
			Selector: &metav1.LabelSelector{
				MatchLabels: data.Label,
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Name:   data.Name,
					Labels: data.Label,
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						// 注意corev1.Container是属组，因为一个pod可包含多个container
						{
							Name: data.Name,
							// 增加deployment的启动命令
							Command: data.Command,
							Image:   data.Image,
							Ports: []corev1.ContainerPort{
								//corev1.ContainerPort也是属组，一个程序可以有多个端口
								{
									Name:          "http",
									Protocol:      corev1.ProtocolTCP,
									ContainerPort: data.ContainerPort,
								},
							},
						},
					},
				},
			},
		},
		Status: appsv1.DeploymentStatus{},
	}
	// 判断健康检查功能是否打开，若打开，则增加健康检查功能.
	if data.HealthCheck {
		deployment.Spec.Template.Spec.Containers[0].ReadinessProbe = &corev1.Probe{
			ProbeHandler: corev1.ProbeHandler{
				HTTPGet: &corev1.HTTPGetAction{
					Path: data.HealthPath,
					Port: intstr.IntOrString{
						// 0表示int32,1表示string
						Type:   0,
						IntVal: data.ContainerPort,
					},
				},
			},
			InitialDelaySeconds: 30,
			TimeoutSeconds:      5,
			PeriodSeconds:       5,
		}
		deployment.Spec.Template.Spec.Containers[0].LivenessProbe = &corev1.Probe{
			ProbeHandler: corev1.ProbeHandler{
				HTTPGet: &corev1.HTTPGetAction{
					Path: data.HealthPath,
					Port: intstr.IntOrString{
						// 0表示int32,1表示string
						Type:   0,
						IntVal: data.ContainerPort,
					},
				},
			},
			InitialDelaySeconds: 30,
			TimeoutSeconds:      5,
			PeriodSeconds:       5,
		}
	}
	// 定义容器的limit和request资源
	deployment.Spec.Template.Spec.Containers[0].Resources.Limits = map[corev1.ResourceName]resource.Quantity{
		corev1.ResourceCPU:    resource.MustParse(data.Cpu),
		corev1.ResourceMemory: resource.MustParse(data.Memory),
	}
	deployment.Spec.Template.Spec.Containers[0].Resources.Requests = map[corev1.ResourceName]resource.Quantity{
		corev1.ResourceCPU:    resource.MustParse(data.Cpu),
		corev1.ResourceMemory: resource.MustParse(data.Memory),
	}
	//调用sdk创建deployment
	_, err = K8s.ClientSet.AppsV1().Deployments(data.Namespace).Create(context.TODO(), deployment, metav1.CreateOptions{})
	if err != nil {
		logger.Error("创建deploy失败，" + err.Error())
		return errors.New("创建deploy失败，" + err.Error())
	}
	return nil
}

func (d *deploy) UpdateDeployment(namespace, content string) (err error) {
	//b,err := ioutil.ReadFile("/Users/chaofeng/files/GoWeb/exercise/teresa/nginxgo.yaml")
	//if err != nil{
	//	logger.Error("读取文件失败")
	//}
	DeploymentRes := &appsv1.Deployment{}
	DeploymentInfo, _ := yaml.ToJSON([]byte(content))
	if err = json.Unmarshal(DeploymentInfo, DeploymentRes); err != nil {
		logger.Error("json unmarshal failed")
		return err
	}
	if _, err = K8s.ClientSet.AppsV1().Deployments(namespace).Update(context.TODO(), DeploymentRes, metav1.UpdateOptions{}); err != nil {
		logger.Error("更新失败," + err.Error())
		return errors.New("更新失败," + err.Error())
	}
	return nil
}

func (d *deploy) DeleteDeployment(deploymentName, namespace string) (err error) {
	err = K8s.ClientSet.AppsV1().Deployments(namespace).Delete(context.TODO(), deploymentName, metav1.DeleteOptions{})
	if err != nil {
		logger.Error("删除deploy失败，" + err.Error())
		return errors.New("删除deploy失败，" + err.Error())
	}
	return nil
}

func (d *deploy) GetDeployNumberNp() (deploysNps []*DeploysNp, err error) {
	namespaceList, err := K8s.ClientSet.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
	if err != nil {
		logger.Error("获取所有namespace失败," + err.Error())
		return nil, errors.New("获取所有namespace失败," + err.Error())
	}
	for _, namespace := range namespaceList.Items {
		deployList, err := K8s.ClientSet.AppsV1().Deployments(namespace.Name).List(context.TODO(), metav1.ListOptions{})
		if err != nil {
			return nil, err
		}
		deploysNp := &DeploysNp{
			Namespace: namespace.Name,
			DeployNum: len(deployList.Items),
		}
		deploysNps = append(deploysNps, deploysNp)
	}
	return deploysNps, nil
}

func (d *deploy) GetDeployScale(deploymentName, namespace string) (num int32, err error) {
	data, err := K8s.ClientSet.AppsV1().Deployments(namespace).GetScale(context.TODO(), deploymentName, metav1.GetOptions{})
	if err != nil {
		logger.Error("获取资源副本数失败," + err.Error())
		return 0, errors.New("获取资源副本数失败," + err.Error())
	}
	num = data.Spec.Replicas
	return num, nil
}

func (d *deploy) UpdateDeployScale(deploymentName, namespace string, scalenumber int32) (replica int32, err error) {
	scaledeploy := &scalev1.Scale{
		ObjectMeta: metav1.ObjectMeta{
			Name:      deploymentName,
			Namespace: namespace,
		},
		Spec: scalev1.ScaleSpec{
			Replicas: scalenumber,
		},
		Status: scalev1.ScaleStatus{},
	}
	newScale, err := K8s.ClientSet.AppsV1().Deployments(namespace).UpdateScale(context.TODO(), deploymentName, scaledeploy, metav1.UpdateOptions{})
	if err != nil {
		logger.Error("扩缩容失败," + err.Error())
		return 0, errors.New("扩缩容失败," + err.Error())
	}
	return newScale.Spec.Replicas, nil
}

func (d *deploy) RestartDeployment(deploymentName, namespace string) (err error) {
	data := fmt.Sprintf(`{"spec":{"template":{"metadata":{"annotations":{"kubectl.kubernetes.io/restartedAt":"%s"}}}}}`, time.Now().String())
	// 对于其他操作比如更换镜像等，需要了解StrategicMergePatchType和applypatch等几种类型的区别。
	_, err = K8s.ClientSet.AppsV1().Deployments(namespace).Patch(context.TODO(), deploymentName, types.StrategicMergePatchType, []byte(data), metav1.PatchOptions{FieldManager: "kubectl-rollout"})
	if err != nil {
		logger.Error("重启失败," + err.Error())
		return errors.New("重启失败," + err.Error())
	}
	return nil
}
